<template>
  <div class="fb-wrap">
    <div class="header">
      <h2>🐤 Flappy Bird</h2>
      <div class="stats">
        <div>分数：{{ score }}</div>
        <div>最高：{{ best }}</div>
      </div>
      <div class="actions">
        <button class="btn" @click="newGame">新开一局</button>
        <button class="btn" @click="togglePause">{{ paused ? '继续' : '暂停' }}</button>
        <button class="btn" @click="toggleHelp">玩法说明</button>
      </div>
    </div>

    <div class="canvas-box">
      <canvas ref="canvas" :width="W" :height="H" tabindex="0" @click="flap" @keydown.prevent="onKey"></canvas>
      <div v-if="gameOver" class="overlay over">
        <div class="msg">撞管子啦！</div>
        <button class="btn" @click="newGame">再来一局</button>
      </div>
    </div>

    <div v-if="showHelp" class="help">
      - 点击或按空格/W/↑ 让小鸟上飞。
      - 穿过管道空隙得分，撞到管子或边界即失败。
      - 可随时暂停/继续。
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'

const W = 360
const H = 480
const canvas = ref(null)
let ctx = null

const score = ref(0)
const best = ref(parseInt(localStorage.getItem('flappyBest') || '0'))
const gameOver = ref(false)
const paused = ref(false)
const showHelp = ref(false)

const birdX = 80
let birdY = H / 2
let birdR = 12
let vel = 0
const gravity = 0.35
const jump = -6

const pipes = ref([]) // { x, gapY, width, gap, scored }
const pipeWidth = 52
const pipeGap = 120
const pipeSpeed = 2.4
let frame = 0
let rafId = 0

function newGame() {
  score.value = 0
  gameOver.value = false
  paused.value = false
  pipes.value = []
  birdY = H / 2
  vel = 0
  frame = 0
  spawnPipe()
  focusCanvas()
}

function focusCanvas() { nextTick(() => canvas.value && canvas.value.focus()) }

function togglePause() { paused.value = !paused.value }
function toggleHelp() { showHelp.value = !showHelp.value }

function flap() {
  if (gameOver.value) return
  vel = jump
}

function onKey(e) {
  const keys = [' ', 'Spacebar', 'Space', 'w', 'W', 'ArrowUp']
  if (keys.includes(e.key)) flap()
}

function spawnPipe() {
  const minGapTop = 60
  const maxGapTop = H - pipeGap - 60
  const gapY = Math.floor(minGapTop + Math.random() * (maxGapTop - minGapTop))
  pipes.value.push({ x: W + 10, gapY, width: pipeWidth, gap: pipeGap, scored: false })
}

function update() {
  if (paused.value || gameOver.value) return
  frame++

  // bird physics
  vel += gravity
  birdY += vel
  if (birdY < birdR) { birdY = birdR; vel = 0 }
  if (birdY > H - birdR) { birdY = H - birdR; gameOver.value = true; onGameEnd() }

  // pipes movement
  for (const p of pipes.value) { p.x -= pipeSpeed }
  // remove off-screen pipes
  while (pipes.value.length && pipes.value[0].x + pipes.value[0].width < -10) pipes.value.shift()
  // spawn new pipe
  if (frame % 100 === 0) spawnPipe()

  // collision + scoring
  for (const p of pipes.value) {
    // collision when bird within pipe x-range
    if (birdX + birdR > p.x && birdX - birdR < p.x + p.width) {
      if (birdY - birdR < p.gapY || birdY + birdR > p.gapY + p.gap) {
        gameOver.value = true
        onGameEnd()
        break
      }
    }
    // score when bird just passes the pipe
    if (!p.scored && p.x + p.width < birdX - birdR) {
      p.scored = true
      score.value += 1
      if (score.value > best.value) { best.value = score.value; localStorage.setItem('flappyBest', String(best.value)) }
    }
  }
}

function draw() {
  if (!ctx) return
  // sky
  ctx.fillStyle = '#87CEEB'
  ctx.fillRect(0, 0, W, H)
  // ground line
  ctx.fillStyle = '#6ab04c'
  ctx.fillRect(0, H - 30, W, 30)

  // pipes
  for (const p of pipes.value) {
    ctx.fillStyle = '#2ecc71'
    // top pipe
    ctx.fillRect(p.x, 0, p.width, p.gapY)
    // bottom pipe
    ctx.fillRect(p.x, p.gapY + p.gap, p.width, H - (p.gapY + p.gap) - 30)
  }

  // bird
  ctx.beginPath()
  ctx.arc(birdX, birdY, birdR, 0, Math.PI * 2)
  ctx.fillStyle = '#f9ca24'
  ctx.fill()
  ctx.closePath()

  // score text
  ctx.fillStyle = '#2d3436'
  ctx.font = 'bold 20px system-ui'
  ctx.fillText('分数: ' + score.value, 10, 24)
}

function loop() {
  update()
  draw()
  rafId = requestAnimationFrame(loop)
}

function onGameEnd() {
  // reveal overlay handled in template; stop motion but keep render
}

onMounted(() => {
  ctx = canvas.value.getContext('2d')
  newGame()
  rafId = requestAnimationFrame(loop)
  window.addEventListener('keydown', onKey)
})

onBeforeUnmount(() => {
  cancelAnimationFrame(rafId)
  window.removeEventListener('keydown', onKey)
})
</script>

<style scoped>
.fb-wrap { max-width: 420px; margin: 16px auto; font-family: system-ui, -apple-system, Segoe UI, PingFang SC, Microsoft YaHei; }
.header { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.stats { display:flex; gap:12px; }
.actions { display:flex; gap:8px; }
.btn { padding:6px 10px; border:none; border-radius:6px; background:#0984e3; color:#fff; cursor:pointer; }
.btn:hover { filter:brightness(1.05); }
.canvas-box { position:relative; margin-top:12px; border-radius:8px; overflow:hidden; box-shadow: 0 4px 12px rgba(0,0,0,.15); }
canvas { display:block; background:#87CEEB; border-radius:8px; outline:none; }
.overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:10px; background:rgba(255,255,255,.7); text-align:center; }
.help { margin-top:10px; background:#f8f6f2; border:1px solid #eee; border-radius:8px; padding:8px 10px; color:#3a4a5a; font-size:14px; }
</style>